[セッション詳説] Amazon DynamoDBのデータモデリング #CMY304 #reinvent
Data modeling with Amazon DynamoDB
本記事はre:Invent 2019のセッション「CMY304 - Data modeling with Amazon DynamoDB」を 開発者視点で噛み砕いた レポート兼解説記事です。
本記事ではセッション内容を元とした形の感想記事ですので、スピーカーが語っていない個人的な感想が多く含まれています。もしオリジナルの内容に絞った情報を知りたい場合は公式のセッション動画およびスライドの公開をお待ちいただければと思います。
Speaker
Alex DeBrie (@alexbdebrie)
Serverless Inc.のEngineering Manager, AWS Data Heroの方です。
DynamoDBのことが学べる「DynamoDBGuide」および「DynamoDBBook.com」を執筆されています。
特に「DynamoDBGuide」はオンラインでDynamoDBが学べる素晴らしいWebコンテンツでした。これからDynamoDBを学ぼうと思われている方はぜひ見ておいた方が良いと思います。
What is Amazon DynamoDB?
改めて、Amazon DynamoDBについてのおさらいをしましょう。
- NoSQLデータベース
- AWSのフルマネージドサービス
- AWS IAMを使ったHTTPSによる認証
- 速く安定したパフォーマンス、そしてスケール可能
Amazon DynamoDBは登場以来、NoSQLデータベースの先進的存在として利用されてきました。DAX (Amazon DynamoDB Accelerator) やオンデマンドキャパシティなど、速さと柔軟性を兼ね備えたアップデートを携えて今日に至ります。
利用事例
改めて語る必要のないくらい、国内・国外ともに多くの事例があるかと思います。
その中でも本セッションで紹介された大規模な事例としては Amazon.com そして Lyft。
Lyftはアメリカで有名な配車マッチングサービスです。Lyftについてはジオロケーション部分で使っているとのことでした。
膨大な数のユーザーが利用するサービスこそ、DynamoDBがうまく活躍する場面と言えますね。
ユースケース
サーバーレスの文脈ではAWS LambdaまたはAWS AppSyncで使うケースが多いですが(Hyper-Ephemeral computeと紹介されていました)、サーバーレスではないアーキテクチャでもよく採用されます。
DynamoDBのキーコンセプト
よく見る4つのキーコンセプトです。
- Table
- Item
- Primary key
- Attributes
この中でもプライマリキーが特に重要なキーコンセプトです。プライマリキーは、以下のいずれかで構成されます。
- Simple primary key (partition key)
- Composite primary key (partition key + sort key)
これは "またの名" をHash KeyとRange Keyと言いますね。むしろ、Partition KeyとSort Keyが "またの名" かも知れません。
Developers.IOでもこれまで多くのプライマリーキーに関する解説記事を公開しています。DynamoDBの設計はすべてプライマリーキーに掛かっていると 言っても過言ではないです。
弊社の都元が執筆した以下のシリーズ、すごく勉強になるのでご覧いただければと思います。電子書籍とかにしても良いと思いますよ、これ。
APIアクション
DynamoDBのデータを操作するには、以下のいずれかを使います。
- Item-based actions (いわゆるCRUDに近い形の操作)
- Query
- Scan
QueryやScanは一覧取得であったり、検索する場合などに利用します。
ポイントはItem-based actionsとQueryには上述のプライマリキーが必須であることです。
QueryはSort keyを範囲で指定することで、複数のアイテムを取得できます。
Scanには必要ありませんが、すべてのアイテムが対象となるため多くのコストがかかります。
Secondary Indexes
プライマリキー1つでは、どうしても要件を満たせない場合があります。そのような時にはセカンダリインデックスを活用する方法が一般的です。
セカンダリインデックスを使うことで、例えばPartition keyとSort keyを逆転させるようなことができます。
データモデリングの戦略と実践
基本としては以下の流れでデータモデリングを実施します。
- ERD(いわゆるER図)からスタート
- アクセスパターンの定義
- プライマリキーとセカンダリインデックスの設計
大事なこと : リレーショナルは一旦忘れる
- 正規化
- JOIN
- テーブルごとのエンティティタイプ
NoSQLにおける設計では、このような発想は一旦忘れ、NoSQLに合った設計が必要となります。逆に合わなすぎる場合はNoSQLが適切な選択肢ではない可能性があります。
作ってみる
ECサイトを題材としたデータモデリングを実践してみます。
- ECサイト
- ユーザーはオーダーを作ることができる
- 1つのオーダーには複数の商品(アイテム)を含むことができる
まずはER図です。正規化したくなるような図ですが、一旦忘れながら考えましょう。
次にアクセスパターンを考えます。ユーザーのアクセスパターンとしては以下のようなものが考えられます。
- ユーザー情報の取得
- ユーザー別のオーダーの取得
- オーダーの商品一覧の取得
- ステータスによる絞り込み
- オーダーの表示
プライマリキー、セカンダリインデックスの設計をしていきます。上記のアクセスパターンを元に、順々にどう実現するか考えて埋めていきます。
属性 | PK | SK |
---|---|---|
User | USER#<username> |
#PROFILE#<username> |
User Address | ||
Order | ||
Order Item |
リレーションシップ
どうしても、いずれかのItemに依存したItemを作りたい場合があります。例えば user_address
は user
に依存する形にしたいわけです。
今回の場合は住所ベースでの検索はないので user
のAttributeとして問題ありません。
ということで User Address
テーブルは不必要になります。
属性 | PK | SK |
---|---|---|
User | USER#<username> |
#PROFILE#<username> |
Order | ||
Order Item |
User:Orderのような、1:Nの場合はどうでしょうか?
この場合はSort Keyを活用します。フォーマットを ORDER#<user>
のようにすることで、そのユーザーのオーダー一覧をQueryにて取得できます。
Orderが埋まりました。
属性 | PK | SK |
---|---|---|
User | USER#<username> |
#PROFILE#<username> |
Order | USER#<username> |
ORDER#<orderId> |
Order Item |
この時、Queryは次のようになります。
PK = USER#mesoko AND BEGINS_WITH(SK, 'ORDER#')
次にOrder:OrderItemsの場合です。
これもSort Keyを活用します。Sort KeyにOrderを一意に特定できれば、オーダーごとのアイテム一覧を取得できます。
属性 | PK | SK |
---|---|---|
User | USER#<username> |
#PROFILE#<username> |
Order | USER#<username> |
ORDER#<orderId> |
Order Item | ITEM#<itemId> |
ORDER#<orderId> |
これで表が完成です。
Inverted index
オーダーのIDがSort Keyで表現されているため、Indexを逆転させたGlobal Secondary Index(GSI)を作るとオーダー単位でユーザー、アイテムがまとまります。
1:Nのリレーションシップのパターン
1:Nのリレーションシップを表現したい場合は、以下のようなパターンのうちのいずれかを採用することがほとんどです。
- Attributeに突っ込む(List or Map)
- Primary key + Query
- Secondary index + Query
しかしながら、このパターンはあくまでも「大規模システムを想定した場合」ということを念頭においてください。例えばあまりデータ量やアクセス量が多くない場合、Scanで済ませた方がシンプルになるかも知れません。
フィルタリング
例えば注文日の期間で絞りたい場合があります。
SELECT * FROM ORDERS WHERE USERNAME = 'mesoko' AND ORDERDATE > GETDATE() - 14
このような場合はFilterExpressionを使います。FilterExpressionはQueryで利用できます。
FilterExpressionが必要とされるアクセスパターンは、例えば以下のようなユースケースです。
- ユーザー別のオーダーの取得
- ステータスによる絞り込み
- オーダーの表示
まず「ユーザー別のオーダーの取得」は、以下のような条件式を指定します。前述しているケースですね。
PK = USER#mesoko AND BEGINS_WITH(SK, 'ORDER#')
次の「ステータスによる絞り込み」。
これを行うには、ステータスと注文日時をマージした新たな属性を作ります。
そして、セカンダリインデックスとしてフィルタできるようにします。
PK = USER#mesoko AND BEGINS_WITH(OrderStatusDate, 'SHIPPED#')
もしこのような属性を "あとから" 増やす場合は、既存データの修正などが入ってきます。ですので、前述されている通りのアクセスパターンを洗い出すという作業が非常に重要になってきます。
まとめ
DynamoDBのモデリングをどのような流れで行われているのか、非常に参考になるセッションでした。改めて、シンプルにまとめられたモデリングの手順を載せます。
- ERD(いわゆるER図)からスタート
- アクセスパターンの定義
- プライマリキーとセカンダリインデックスの設計
特にアクセスパターンが出しきれていなかったり、想定しきれていなかったりすると、設計し直しになる可能性すらあり得ます。サービスイン後にこれが発生すると、データ移行なども発生するかと思いますので大変ですよね。使い方をしっかりと考え、データモデリングに挑みましょう。